﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Xml.Linq;
using NthDownload.Common.Plugins;
using NthDownload.Common.Utils;
using NthDownload.Contracts;
using NthDownload.Contracts.Plugins.BandwidthProviders;
using NthDownload.Composition;
using System.Threading;
using System.Linq;

namespace NthDownload.BL.Serialization
{
    public class NthProfileXmlSerializationService : ISerializationService<NthProfile, string>
    {
        #region Constructor

        public NthProfileXmlSerializationService()
        {
            ImportService.TrySatisfyImports(this);
        }

        #endregion Constructor

        #region Properties

        /// <summary>
        /// The available bandwidth provider plugins
        /// </summary>
        [ImportMany(typeof(IBandwidthProviderPlugin), AllowRecomposition = true)]
        public List<IBandwidthProviderPlugin> BandwidthProviderPlugins { get; set; }

        /// <summary>
        /// The Synchronization Context of the executing thread
        /// </summary>
        [Import(typeof(SynchronizationContext), AllowRecomposition = true)]
        public SynchronizationContext UISynchronizationContext { get; set; }

        #endregion Properties

        #region Methods

        public string Serialize(NthProfile obj)
        {
            if (obj == null)
            {
                throw new Exception("Can't serialize a null profile.");
            }

            //
            // Load all download serialization services
            //
            Dictionary<string, IBandwidthProviderXmlSerializationService> bandwidthProviderXmlSerializationServices = new Dictionary<string, IBandwidthProviderXmlSerializationService>();

            foreach (IBandwidthProviderPlugin bandwidthProviderPlugin in BandwidthProviderPlugins)
            {
                string contractName = BandwidthProviderPluginContractNameService.CreateBandwidthProviderXmlSerializationServiceContractName(bandwidthProviderPlugin.GUID);
                IBandwidthProviderXmlSerializationService bandwidthProviderXmlSerializationService;
                if (ImportService.TryGetExportValue(contractName, out bandwidthProviderXmlSerializationService))
                {
                    bandwidthProviderXmlSerializationServices.Add(bandwidthProviderPlugin.GUID, bandwidthProviderXmlSerializationService);
                }
            }

            //
            // Build XML
            //
            XElement rootElement = new XElement("Profile");

            XElement bandwidthProvidersElement = new XElement("BandwidthProviders");
            rootElement.Add(bandwidthProvidersElement);

            foreach (IBandwidthProvider bandwidthProvider in obj.BandwidthProviders)
            {
                IBandwidthProviderXmlSerializationService bandwidthProviderXmlSerializationService;
                if (!bandwidthProviderXmlSerializationServices.TryGetValue(bandwidthProvider.ParentGUID, out bandwidthProviderXmlSerializationService)) continue;

                XElement bandwidthProviderElement = new XElement("BandwidthProvider");
                XElement bandwidthProviderDataElement = bandwidthProviderXmlSerializationService.Serialize(bandwidthProvider);

                bandwidthProviderElement.SetAttributeValue("ParentGUID", bandwidthProvider.ParentGUID);
                bandwidthProviderElement.SetAttributeValue("DataElementName", bandwidthProviderDataElement.Name);
                bandwidthProviderElement.Add(bandwidthProviderDataElement);

                bandwidthProvidersElement.Add(bandwidthProviderElement);
            }

            ISerializationService<NthQueue, XElement> nthQueueSerializationService = new NthQueueXmlSerializationSerivce(obj.BandwidthProviders);
            XElement queuesElement = new XElement("Queues");
            rootElement.Add(queuesElement);

            foreach (NthQueue queue in obj.Queues)
            {
                XElement queueElement = new XElement("Queue");
                XElement queueDataElement = nthQueueSerializationService.Serialize(queue);

                queueElement.SetAttributeValue("DataElementName", queueDataElement.Name);
                queueElement.Add(queueDataElement);

                queuesElement.Add(queueElement);                
            }

            return rootElement.ToString();
        }

        public NthProfile Deserialize(string data)
        {
            //
            // Read data from element tree
            //
            NthProfile profile = null;
            UISynchronizationContext.Send(state => profile = new NthProfile(), null);
            if (profile == null)
            {
                throw new Exception("Profile initialization failed.");
            }

            Deserialize(data, profile);

            return profile;
        }

        public void Deserialize(string data, NthProfile obj)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }

            if (obj == null)
            {
                throw new ArgumentNullException("obj");
            }

            NthProfile profile = obj;

            //
            // Check if any of the queues in the profile have running downloads
            //
            if (profile.Queues.Any(q => q.HasRunningDownloads))
            {
                throw new Exception("Can't update a profile that contains a queue with running downloads");
            }

            //
            // Load all download serialization services
            //
            Dictionary<string, IBandwidthProviderXmlSerializationService> bandwidthProviderXmlSerializationServices = new Dictionary<string, IBandwidthProviderXmlSerializationService>();
            foreach (IBandwidthProviderPlugin bandwidthProviderPlugin in BandwidthProviderPlugins)
            {
                string contractName = BandwidthProviderPluginContractNameService.CreateBandwidthProviderXmlSerializationServiceContractName(bandwidthProviderPlugin.GUID);
                IBandwidthProviderXmlSerializationService bandwidthProviderXmlSerializationService;
                if (ImportService.TryGetExportValue(contractName, out bandwidthProviderXmlSerializationService))
                {
                    bandwidthProviderXmlSerializationServices.Add(bandwidthProviderPlugin.GUID, bandwidthProviderXmlSerializationService);
                }
            }

            //
            // Parse data into element tree
            //
            XElement rootElement = XElement.Parse(data);

            profile.BandwidthProviders.Clear();
            XElement bandwidthProvidersElement = XmlUtils.GetDecendant(rootElement, "BandwidthProviders");
            foreach (XElement bandwidthProviderElement in bandwidthProvidersElement.Descendants("BandwidthProvider"))
            {
                string parentGUID = (string)bandwidthProviderElement.Attribute("ParentGUID") ?? "";

                IBandwidthProviderXmlSerializationService bandwidthProviderXmlSerializationService;
                if (!bandwidthProviderXmlSerializationServices.TryGetValue(parentGUID, out bandwidthProviderXmlSerializationService)) continue;

                string dataElementName = (string)bandwidthProviderElement.Attribute("DataElementName") ?? "";
                XElement bandwidthProviderDataElement = bandwidthProviderElement.Descendants(dataElementName).FirstOrDefault();

                if (bandwidthProviderDataElement != null)
                {
                    profile.AddBandwidthProvider(bandwidthProviderXmlSerializationService.Deserialize(bandwidthProviderDataElement));
                }
            }

            NthQueueXmlSerializationSerivce nthQueueSerializationSerivce = null;
            UISynchronizationContext.Send(state => nthQueueSerializationSerivce = new NthQueueXmlSerializationSerivce(profile.BandwidthProviders), null);
            if (nthQueueSerializationSerivce == null)
            {
                throw new Exception("Queue serialization service initialization failed.");
            }

            profile.Queues.Clear();
            XElement queuesElement = XmlUtils.GetDecendant(rootElement, "Queues");
            foreach (XElement queueElement in queuesElement.Descendants("Queue"))
            {
                string dataElementName = (string)queueElement.Attribute("DataElementName") ?? "";
                XElement queueDataElement = queueElement.Descendants(dataElementName).FirstOrDefault();

                if (queueDataElement != null)
                {
                    NthQueue queue = nthQueueSerializationSerivce.Deserialize(queueDataElement);
                    UISynchronizationContext.Send(state => profile.AddQueue(queue), null);
                }
            }
        }

        #endregion Methods
    }
}
